/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.    
 */
using System;
using Parser = Velocity4Net.Runtime.Parse.Parser;
using IntrospectionCacheData = Velocity4Net.Util.Introspection.IntrospectionCacheData;
using Introspector = Velocity4Net.Util.Introspection.Introspector;
using MethodInvocationException = Velocity4Net.Errors.MethodInvocationException;
using InternalContextAdapter = Velocity4Net.Ctx.InternalContextAdapter;
using Velocity4Net.Util.Introspection;
using Velocity4Net.Errors;

namespace Velocity4Net.Runtime.Parse.AST
{

    /// <summary>  ASTIdentifier.java
    /// *
    /// Method support for identifiers :  $foo
    /// *
    /// mainly used by ASTRefrence
    /// *
    /// Introspection is now moved to 'just in time' or at render / execution
    /// time. There are many reasons why this has to be done, but the
    /// primary two are   thread safety, to remove any context-derived
    /// information from class member  variables.
    /// *
    /// </summary>
    /// <author> <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
    /// </author>
    /// <author> <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
    /// </author>
    /// <version> $Id: ASTIdentifier.cs,v 1.4 2003/10/27 13:54:10 corts Exp $
    ///
    /// </version>
    public class ASTIdentifier : SimpleNode
    {
        private System.String identifier = "";

        /**
     *  This is really immutable after the init, so keep one for this node
     */
        protected Info uberInfo;

        /**
     * Indicates if we are running in strict reference mode.
     */
        protected bool strictRef = false;
        public ASTIdentifier(int id) : base(id) { }

        public ASTIdentifier(Parser p, int id) : base(p, id) { }

        /// <summary>  simple init - don't do anything that is context specific.
        /// just get what we need from the AST, which is static.
        /// </summary>
        public override System.Object Init(InternalContextAdapter context, System.Object data)
        {
            base.Init(context, data);

            identifier = FirstToken.image;

            uberInfo = new Info(TemplateName, Line, Column);

            strictRef = rsvc.GetBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false);

            return data;
        }

        /// <summary>  invokes the method on the object passed in
        /// </summary>
        public override System.Object Execute(System.Object o, InternalContextAdapter context)
        {
            VelPropertyGet vg = null;

            try
            {
                /*
                 *  first, see if we have this information cached.
                 */

                IntrospectionCacheData icd = context.ICacheGet(this);

                /*
                 * if we have the cache data and the class of the object we are
                 * invoked with is the same as that in the cache, then we must
                 * be allright.  The last 'variable' is the method name, and
                 * that is fixed in the template :)
                 */

                if (icd != null && (o != null) && (icd.contextData == o.GetType()))
                {
                    vg = (VelPropertyGet)icd.thingy;
                }
                else
                {
                    /*
                     *  otherwise, do the introspection, and cache it.  Use the
                     *  uberspector
                     */

                    vg = rsvc.getUberspect().getPropertyGet(o, identifier, uberInfo);

                    if (vg != null && vg.isCacheable() && (o != null))
                    {
                        icd = new IntrospectionCacheData();
                        icd.contextData = o.GetType();
                        icd.thingy = vg;
                        context.ICachePut(this, icd);
                    }
                }
            }

            /**
             * pass through application level runtime exceptions
             */
            catch (System.ApplicationException e)
            {
                throw e;
            }
            catch (System.Exception e)
            {
                String msg = "ASTIdentifier.execute() : identifier = " + identifier;
                log.error(msg, e);
                throw new VelocityException(msg, e);
            }

            /*
             *  we have no getter... punt...
             */

            if (vg == null)
            {
                if (strictRef)
                {
                    throw new MethodInvocationException("Object '" + o.GetType().Name +
                        "' does not contain property '" + identifier + "'", null, identifier,
                        uberInfo.getTemplateName(), uberInfo.getLine(), uberInfo.getColumn());
                }
                else
                {
                    return null;
                }
            }

            /*
             *  now try and execute.  If we get a MIE, throw that
             *  as the app wants to get these.  If not, log and punt.
             */
            try
            {
                return vg.invoke(o);
            }
            catch (System.Reflection.TargetInvocationException ite)
            {
                /*
                 *  if we have an event cartridge, see if it wants to veto
                 *  also, let non-Exception Throwables go...
                 */

               System. Exception t = ite.InnerException;
                if (t is System. Exception)
                {
                    try
                    {
                        //return EventHandlerUtil.methodException(rsvc, context, o.getClass(), vg.getMethodName(),
                        //        (Exception)t);
                    }

                    /**
                     * If the event handler throws an exception, then wrap it
                     * in a MethodInvocationException.  Don't pass through RuntimeExceptions like other
                     * similar catchall code blocks.
                     */
                    catch (System.Exception e)
                    {
                        throw;
                        //throw new MethodInvocationException(
                        //  "Invocation of method '" + vg.getMethodName() + "'"
                        //  + " in  " + o.GetType()
                        //  + " threw exception "
                        //  + ite.getTargetException().toString(),
                        //  ite.getTargetException(), vg.getMethodName(), getTemplateName(), this.getLine(), this.getColumn());
                    }
                }
                else
                {
                    /*
                     * no event cartridge to override. Just throw
                     */
                    throw;
                    //throw new MethodInvocationException(
                    //"Invocation of method '" + vg.getMethodName() + "'"
                    //+ " in  " + o.getClass()
                    //+ " threw exception "
                    //+ ite.getTargetException().toString(),
                    //ite.getTargetException(), vg.getMethodName(), getTemplateName(), this.getLine(), this.getColumn());


                }
            }
            catch (InvalidOperationException iae)
            {
                return null;
            }
            /**
             * pass through application level runtime exceptions
             */
            catch (ApplicationException e)
            {
                throw e;
            }
            catch (System.Exception e)
            {
                String msg = "ASTIdentifier() : exception invoking method "
                            + "for identifier '" + identifier + "' in "
                            + o.GetType();
                log.error(msg, e);
                throw new VelocityException(msg, e);
            }
            return null;
        }
    }
}
